17.3 启动

在为对象分配堆内存后,mallocgc函数会检查垃圾回收触发条件,并依照相关状态启动或参与辅助回收。

malloc.go

func mallocgc(size uintptr,typ*_type,flags uint32)unsafe.Pointer{ …

// 直接分配黑色对象 if gcphase== _GCmarktermination||gcBlackenPromptly{ systemstack(func() { gcmarknewobject_m(uintptr(x),size) }) }

// 检查垃圾回收触发条件 if shouldhelpgc&&shouldtriggergc() { // 启动并发垃圾回收 startGC(gcBackgroundMode,false) }else if gcBlackenEnabled!=0{ // 辅助参与回收任务 gcAssistAlloc(size,shouldhelpgc) }else if shouldhelpgc&&bggc.working!=0{ // 让出资源 gp:=getg() if gp!=gp.m.g0&&gp.m.locks0&&gp.m.preemptoff "" { Gosched() } } }

func shouldtriggergc()bool{ return memstats.heap_live>=memstats.next_gc&&atomicloaduint(&bggc.working) ==0 }

heap_live是活跃对象总量,不包括那些尚未被清理的白色对象。

垃圾回收默认以全并发模式运行,但可以用环境变量或参数禁用并发标记和并发清理。GC goroutine一直循环,直到符合触发条件时被唤醒。

mgc.go

func startGC(mode int,forceTrigger bool) { // 判断GODEBUG环境变量 //1: 禁用并发标记 //2: 禁用并发标记和并发清理 if debug.gcstoptheworld==1{ mode=gcForceMode }else if debug.gcstoptheworld==2{ mode=gcForceBlockMode }

// 同步阻塞模式 if mode!=gcBackgroundMode{ gc(mode) return }

// 检查触发条件 if!(forceTrigger||shouldtriggergc()) { return }

// 全局变量bggc保存GC状态 // 创建或唤醒GC goroutine if!bggc.started{ bggc.working=1 bggc.started=true go backgroundgc() }else if bggc.working==0{ bggc.working=1

 // 唤醒 
ready(bggc.g,0) 

} }

var bggc struct{ g *g//GC goroutine working uint // 是否正处于工作状态 started bool // 是否已创建 }

func backgroundgc() { bggc.g=getg() for{ gc(gcBackgroundMode) bggc.working=0

 // 休眠,等待再次被唤醒 
goparkunlock(&bggc.lock, "Concurrent GC wait",traceEvGoBlock,1) 

} }

经过种种手段的优化调整,在整个回收周期,STW被缩短到有限的几个片段,这让程序实时响应有了很大改善。

新GC的表现虽说不上惊艳,但足以让人相当惊喜。它代表了Go不断进化,以及开发团队追求卓越的精神,这让我对其前景更为看好。不过,当前版本有很多过渡痕迹,甚至代码和文档有对不上的地方。这种情形曾出现在1.3里,或许下一个版本才是最佳选择。

是否完全去掉STW?是否能优化写屏障的性能?Go还有很多问题尚待解决。

并发模式(Background Mode)垃圾回收过程示意图:

---------------------+--------------------------------------------------- | OFF+ ------ 准备MarkWorker/P,使其休眠待命 | stop | B:1 BE:1]SCAN+ | start | + ------ 并发扫描,将灰色对象放入队列 | 对白色对象的引用修改被写屏障捕获 | Malloc分配白色对象 | MarkWorker被唤醒,开始标记任务 | MARK+ | + ------ 等待第一轮标记结束 | 第一轮处理的是并发扫描捕获的灰色对象,不包括新分配白色对象 | + ------ 重新扫描DATA、BSS区域 | 扫描新分配白色对象 | + ------ 等待第二轮标记结束 | stop | [BE:0] + | MARK TERMINATION+ | + ------STW冻结,完成最终标记 | [WB:0]OFF+ | + ------ 并发清理 | start | STW:StopTheWorld WB:WriteBarrierEnabled BE:BlackenEnabled

整个过程被封装在有些庞大的gc函数里。

mgc.go

func gc(mode int) { // 清理掉意外遗留的span for gosweepone() != ^uintptr(0) { sweep.nbgsweep++ }

// 创建MarkWorker(休眠状态) if mode==gcBackgroundMode{ gcBgMarkStartWorkers() }

//STW:STOP systemstack(stopTheWorldWithSema)

// 确保在进入扫描状态前,环境已清理干净 systemstack(finishsweep_m)

// 处理sync.Pool clearpools()

// 重置全局状态变量work gcResetMarkState()

// ---OFF(STW:STOP) -----------------------------------------------

// 并发标记模式 if mode==gcBackgroundMode{ // 控制器 gcController.startCycle()

systemstack(func() { 
     // 启用写屏障 
    setGCPhase(_GCscan) 

     // 初始化相关状态和信号 
    gcBgMarkPrepare() 

     // 允许黑色对象标记 
    atomicstore(&gcBlackenEnabled,1) 

     //STW:START
    startTheWorldWithSema() 

     // ---SCAN(STW:START) ------------------------------ 

     // 并发扫描 
    gcscan_m() 

    setGCPhase(_GCmark) 
 }) 

 // ---MARK(STW:START) ------------------------------------- 

 // 等待MarkWorker发回第一轮任务结束信号 
work.bgMark1.clear() 
work.bgMark1.wait() 

 // 第二轮扫描,目标新增白色对象和剩余区段 
systemstack(func() { 
     //DATA、BSS保存全局变量 
    markroot(nil, _RootData) 
    markroot(nil, _RootBss) 

    gcBlackenPromptly=true
    forEachP(func(_p_ *p) { 
         _p_.gcw.dispose() 
     }) 
 }) 

 // 等待MarkWorker发回第二轮任务结束信号 
work.bgMark2.clear() 
work.bgMark2.wait() 

 //STW:STOP
systemstack(stopTheWorldWithSema) 

 // 将所有P.gcw上交全局队列 
gcFlushGCWork() 

gcController.endCycle() 

}else{ // 阻塞模式(mode!=gcBackgroundMode) gcResetGState() }

// ---MARK TERMINATION(STW:STOP) ----------------------------------

// 禁用黑色标记操作(MarkWorker停止工作) atomicstore(&gcBlackenEnabled,0) gcBlackenPromptly=false setGCPhase(_GCmarktermination)

// 完成最终标记工作 // 如果是阻塞模式,因为没有前期的扫描和标记操作,那么此处完成全部标记 systemstack(func() { gcMark(startTime) })

// ---OFF(STW:STOP) -----------------------------------------------

systemstack(func() { // 关闭写屏障 setGCPhase(_GCoff)

 // 开启清理操作(并发或阻塞) 
gcSweep(mode) 

})

// 全部工作完成 //STW:START systemstack(startTheWorldWithSema) }